下面我將介紹如何透過Shader製作一個光暈。
分三個階段:
我已經準備好範本程式碼。如果看到畫面是淡藍色,那代表這是正常顯示的範本。
https://codepen.io/umas-sunavan/pen/gOzEqwa?editors=1010
我們建立一個球體。球體的材質是由ShaderMaterial
組成。而ShaderMaterial
所使用的Shader程式碼,來自HTML。
+const addSphere = () => {
+ const vertex = document.getElementById('vertexShader').innerHTML
+ const fragment = document.getElementById('fragmentShader').innerHTML
+ const geo = new THREE.SphereGeometry(5,50,50)
+ const mat = new THREE.ShaderMaterial({
+ vertexShader: vertex,
+ fragmentShader: fragment,
+ })
+ const mesh = new THREE.Mesh(geo, mat)
+ scene.add(mesh)
+ return mesh
+}
+addSphere()
HTML:
+<div style="display: none">
+ <p id="fragmentShader">
+ #ifdef GL_ES
+ precision mediump float;
+ #endif
+ void main(void){
+ gl_FragColor=vec4(0.,0.,0.2,1.);
+ }
+ </p>
+ <p id="vertexShader">
+ void main(void){gl_Position=projectionMatrix*modelViewMatrix*vec4(position, 1.0);
+ }
+ </p>
+</div>
在vertex shader我們添加vertexNormal
。vertexNormal
是我們即將從vertex shader傳入到fragment shader的變數,而normal則是three.js提供的變數。
+varying vec3 vertexNormal;
void main(void){
+ vertexNormal = normal;
void main(void){gl_Position=projectionMatrix*modelViewMatrix*vec4(position, 1.0);
}
在fragment shader我們添加。
#ifdef GL_ES
precision mediump float;
#endif
+ varying vec3 vertexNormal;
void main(void){
gl_FragColor=vec4(0.,0.,0.2,1.);
}
過去我們有介紹過fragment shader,gl_FragColor會是每個像素最終的顏色。如果我們設置成vec4(0.2,0.2,0.4,1.)
,那大概長這樣:
我們加上「神秘的程式碼」:
varying vec3 vertexNormal;
void main(void){
+ float intensity = 1.05 - dot(vertexNormal, vec3(0.,0.,1.));
+ vec3 atmosphere = vec3(.3, .6, 1.) * intensity;
+ gl_FragColor=vec4(atmosphere,0.) + vec4(0.,0.,0.2,1.);
- gl_FragColor=vec4(0.,0.,0.2,1.);
}
如此一來,光暈就完成了。
https://codepen.io/umas-sunavan/pen/KKREJzq
你現在可能會充滿問號,對於為什麼可以形成光暈有滿滿的問號。
不過沒關係,我們接下來將用兩篇的篇幅解釋為什麼光暈可以生成。並且透過這個案例,不斷挖掘shader的原理,最後完整的釐清光暈的運作方式。
前兩篇,我們先講解原理,再討論實作方式。這樣的作法非常教科書。很多時候我們在Shader研究原理時,順序往往是倒過來的。意思是:我們常常會先看到一個很好的Shader程式碼,但不知道其原理,不斷推敲回去。
所以從本篇開始,我會從程式碼去推敲其作用,並且延伸其作用的概念,最後講解整個原理。